﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace Yz.Core.DBUtility
{
    /// <summary>
    ///     解析Expression表达式树
    /// </summary>
    public class ResolveExpression
    {
        public Dictionary<string, string> Argument;
        private int index;
        public string SqlWhere;

        public ResolveExpression()
        {
        }

        public ResolveExpression(Expression expression)
        {
            index = 0;
            Argument = new Dictionary<string, string>();
            SqlWhere = Resolve(expression);
        }

        /// <summary>
        ///     解析lamdba，生成Sql查询条件
        /// </summary>
        /// <param name="expression"></param>
        /// <returns></returns>
        public void ResolveToSql(Expression expression)
        {
            index = 0;
            Argument = new Dictionary<string, string>();
            SqlWhere = Resolve(expression);
        }

        private object GetValue(Expression expression)
        {
            if (expression is ConstantExpression)
                return (expression as ConstantExpression).Value;
            if (expression is UnaryExpression)
            {
                var unary = expression as UnaryExpression;
                var lambda = Expression.Lambda(unary.Operand);
                var fn = lambda.Compile();
                return fn.DynamicInvoke(null);
            }

            if (expression is MemberExpression)
            {
                var member = expression as MemberExpression;
                var name = member.Member.Name;
                var constant = member.Expression as ConstantExpression;
                if (constant == null)
                    throw new Exception("取值时发生异常" + member);
                return constant.Value.GetType().GetFields().First(x => x.Name == name).GetValue(constant.Value);
            }

            throw new Exception("无法获取值" + expression);
        }

        private string Resolve(Expression expression)
        {
            if (expression is LambdaExpression)
            {
                var lambda = expression as LambdaExpression;
                expression = lambda.Body;
                return Resolve(expression);
            }

            if (expression is BinaryExpression) //解析二元运算符
            {
                var binary = expression as BinaryExpression;
                if (binary.Left is MemberExpression)
                {
                    var value = GetValue(binary.Right);
                    return ResolveFunc(binary.Left, value, binary.NodeType);
                }

                if (binary.Left is MethodCallExpression &&
                    (binary.Right is UnaryExpression || binary.Right is MemberExpression))
                {
                    var value = GetValue(binary.Right);
                    return ResolveLinqToObject(binary.Left, value, binary.NodeType);
                }
            }

            if (expression is UnaryExpression) //解析一元运算符
            {
                var unary = expression as UnaryExpression;
                return Resolve(unary.Operand);
                //if (unary.Operand is MethodCallExpression)
                //{
                //    return ResolveLinqToObject(unary.Operand, false);
                //}
                //if (unary.Operand is MemberExpression)
                //{
                //    return ResolveFunc(unary.Operand, false, ExpressionType.Equal);
                //}
            }

            if (expression is MethodCallExpression) //解析扩展方法
                return ResolveLinqToObject(expression, true);
            if (expression is MemberExpression) //解析属性。。如x.Deletion
                return ResolveFunc(expression, true, ExpressionType.Equal);
            if (expression is MemberInitExpression)
            {
                var mbsql = "";
                var mbInit = expression as MemberInitExpression;
                foreach (var item in mbInit.Bindings)
                {
                    var mbItem = item as MemberAssignment;
                    if (mbItem.Expression is MethodCallExpression)
                        mbsql += ResolveLinqToObject(mbItem.Expression, true);
                }

                return mbsql;
            }

            var body = expression as BinaryExpression;
            if (body == null)
                throw new Exception("无法解析" + expression);
            var Operator = GetOperator(body.NodeType);
            var Left = Resolve(body.Left);
            var Right = Resolve(body.Right);
            var Result = string.Format("({0} {1} {2})", Left, Operator, Right);
            return Result;
        }

        /// <summary>
        ///     根据条件生成对应的sql查询操作符
        /// </summary>
        /// <param name="expressiontype"></param>
        /// <returns></returns>
        private string GetOperator(ExpressionType expressiontype)
        {
            switch (expressiontype)
            {
                case ExpressionType.And:
                    return "and";
                case ExpressionType.AndAlso:
                    return "and";
                case ExpressionType.Or:
                    return "or";
                case ExpressionType.OrElse:
                    return "or";
                case ExpressionType.Equal:
                    return "=";
                case ExpressionType.NotEqual:
                    return "<>";
                case ExpressionType.LessThan:
                    return "<";
                case ExpressionType.LessThanOrEqual:
                    return "<=";
                case ExpressionType.GreaterThan:
                    return ">";
                case ExpressionType.GreaterThanOrEqual:
                    return ">=";
                default:
                    throw new Exception(string.Format("不支持{0}此种运算符查找！" + expressiontype));
            }
        }


        private string ResolveFunc(Expression left, object value, ExpressionType expressiontype)
        {
            var Name = (left as MemberExpression).Member.Name;
            var Operator = GetOperator(expressiontype);
            var Value = value.ToString();
            var CompName = SetArgument(Name, Value);
            var Result = string.Format("({0} {1} {2})", Name, Operator, CompName);
            return Result;
        }

        private string ResolveLinqToObject(Expression expression, object value, ExpressionType? expressiontype = null)
        {
            var MethodCall = expression as MethodCallExpression;
            var method = MethodCall.Method;
            var IsCandidateMethod = method.IsAssembly && (method.Name == "MergeAs" || method.Name == "IncludeSpan");
            if (IsCandidateMethod) return "";
            var MethodName = MethodCall.Method.Name;
            switch (MethodName) //这里其实还可以改成反射调用，不用写switch
            {
                case "Contains":
                    if (MethodCall.Object != null)
                        return Like(MethodCall);
                    return In(MethodCall, value);
                case "Count":
                    return Len(MethodCall, value, expressiontype.Value);
                case "LongCount":
                    return Len(MethodCall, value, expressiontype.Value);
                case "Where":
                default:
                    var sql = "";
                    for (var i = 0; i < MethodCall.Arguments.Count; i++)
                    {
                        sql += Resolve(MethodCall.Arguments[i]);
                    }
                    return sql;
            }
        }

        private string SetArgument(string name, string value)
        {
            name = "@" + name;
            var temp = name;
            while (Argument.ContainsKey(temp))
            {
                temp = name + index;
                index = index + 1;
            }

            Argument[temp] = value;
            return temp;
        }

        private string In(MethodCallExpression expression, object isTrue)
        {
            var Argument1 = expression.Arguments[0];
            var Argument2 = expression.Arguments[1] as MemberExpression;
            var fieldValue = GetValue(Argument1);
            var array = fieldValue as object[];
            var SetInPara = new List<string>();
            for (var i = 0; i < array.Length; i++)
            {
                var Name_para = "InParameter" + i;
                var Value = array[i].ToString();
                var Key = SetArgument(Name_para, Value);
                SetInPara.Add(Key);
            }

            var Name = Argument2.Member.Name;
            var Operator = Convert.ToBoolean(isTrue) ? "in" : " not in";
            var CompName = string.Join(",", SetInPara);
            var Result = string.Format("{0} {1} ({2})", Name, Operator, CompName);
            return Result;
        }

        private string Like(MethodCallExpression expression)
        {
            var argument = expression.Arguments[0];
            var Temp_Vale = GetValue(argument);
            var Value = string.Format("%{0}%", Temp_Vale);
            var Name = (expression.Object as MemberExpression).Member.Name;
            var CompName = SetArgument(Name, Value);
            var Result = string.Format("{0} like {1}", Name, CompName);
            return Result;
        }

        private string Len(MethodCallExpression expression, object value, ExpressionType expressiontype)
        {
            object Name = (expression.Arguments[0] as MemberExpression).Member.Name;
            var Operator = GetOperator(expressiontype);
            var CompName = SetArgument(Name.ToString(), value.ToString());
            var Result = string.Format("len({0}){1}{2}", Name, Operator, CompName);
            return Result;
        }
    }
}
